#include "led.h"
#include "tim3.h"
#include "tim4.h"

const IOList hub08_gpio[20] = {                                    // HUB08
                                                                   //  右边
    {GPIOB, {GPIO_Pin_0, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // A
    {GPIOB, {GPIO_Pin_2, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // B
    {GPIOB, {GPIO_Pin_11, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0}, // C
    {GPIOB, {GPIO_Pin_13, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0}, // D
    {GPIOB, {GPIO_Pin_15, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0}, // G1
    {GPIOA, {GPIO_Pin_12, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0}, // G2
    {GPIOB, {GPIO_Pin_6, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // LAT
    {GPIOB, {GPIO_Pin_8, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // CLK
                                                                   // 左边
    {GPIOB, {GPIO_Pin_1, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // GND
    {GPIOB, {GPIO_Pin_10, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0}, // GND
    {GPIOB, {GPIO_Pin_12, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0}, // GND
    {GPIOB, {GPIO_Pin_14, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0}, // OE
    {GPIOA, {GPIO_Pin_11, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0}, // R1
    {GPIOB, {GPIO_Pin_5, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // R2
    {GPIOB, {GPIO_Pin_7, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // GND
    {GPIOB, {GPIO_Pin_9, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // GND
                                                                   // 其他
    {GPIOA, {GPIO_Pin_2, GPIO_Speed_50MHz, GPIO_Mode_Out_PP}, 0},  // LED
    {0, {0, 0, 0}, 0}};

volatile uint8_t new_data_in = 0;                   // 记录是否有新数据要显示
struct display_param d = {0};                       // 用于向中断函数传递参数
volatile uint8_t display_buf_index = 0;             // 当前显示的缓冲区索引
volatile uint8_t *text_buf = 0;                     // 存储文字
volatile uint8_t *text_buf_backup = 0;              // 文字地址备份
uint8_t pix_buf[32 + 10] = {0};                     // 用于读取点阵的缓冲区32字节
volatile uint16_t display_buf[16 * BUF_SIZE] = {0}; // 缓冲区大小
volatile uint16_t display_nbit_offset = 0;          // 偏移多少个bit显示
volatile uint16_t display_nbit = SCREEN_WIDTH;      // 要移位多少位到显示器
volatile uint16_t left_shift_time = 0;              // 多久向左移一位
volatile uint32_t left_shift_count = 0;             // 左移显示了多少次
volatile uint32_t head = 0;                         // 循环显存头的位置
volatile uint32_t tail = 8;                         // 循环显存尾的位置

// HUB08 双色接口 需要11根控制线.这里初始化这些IO口
void led_init()
{
    gpio_init((IOList *)hub08_gpio);
    TIM3_Init();
    tim4_init();
}

void led_turn_off()
{
    TIM_Cmd(TIM3, DISABLE);
}

void led_turn_on()
{
    TIM_Cmd(TIM3, ENABLE);
}

/* 将读取的字体放到显存中,position是位置0表示最左边,每加1表示向右移动8个像素 */
void insert_8x16_to_display_buf(uint8_t *buf, uint16_t position)
{
    uint8_t *p = (uint8_t *)display_buf + position;
    for (uint8_t i = 0; i < 16; i++)
    {
        *p = *buf;
        buf += 1;
        p += BUF_SIZE * 2;
    }
}

void insert_16x16_to_display_buf(uint8_t *buf, uint16_t position)
{
    insert_8x16_to_display_buf(buf, position);
    position = position == BUF_SIZE * 2 - 1 ? 0 : position + 1;
    insert_8x16_to_display_buf(buf + 16, position);
}

/* 清空显存内容 */
void clear_8x16_to_display_buf(uint16_t position)
{
    uint8_t *p = (uint8_t *)display_buf + position;
    for (uint8_t i = 0; i < 16; i++)
    {
        *p = 0;
        p += BUF_SIZE * 2;
    }
}

/* 读点阵数据,可以读字库中的,也可以读程序内置字库,
返回1表示是ascii 返回2表示,前题要要求编码是正确的
本程序不具备猜测纠正编码的功能 */
uint8_t read_font(uint8_t *buf, uint16_t GB2312_Code)
{
    uint32_t addr = 0;
    uint8_t region = GB2312_Code;
    uint8_t position = GB2312_Code >> 8;
    /* 1到9区的非汉字字符  16到87区的汉字在Flash中读
    这两部分本来在字库中是紧挨的,中间我插入了 6 * 94 * 32个0*/
    if (region >= 0xA1)
    {
        addr |= ((region - 0xA1) * 94 + (position - 0xA1)) * 32 + ZK_OFFSET;
        flash_read_data(addr, buf, 32);
        return 2;
    }
    /* ASCII字符在程序中数组中读 */
    else if (region >= 0x20)
    {
        uint8_t *psrc = (uint8_t *)&ascii_8x16_font[((uint8_t)GB2312_Code - 0x20) * 16];
        for (uint8_t n = 0; n < 16; n++)
        {
            *buf++ = *psrc++;
        }
        return 1;
    }
    return 0;
}

/* 显示字符串 */
void display_text_left_shift(uint8_t *text)
{
    // 创建显示任务,就不用管了,其余交给中断函数
    d.display_nbit = SCREEN_WIDTH;
    d.left_shift_time = MS_PER_SHIFT;
    d.head = 0;
    d.tail = SCREEN_WIDTH / 8;
    d.text_buf = text;
    new_data_in = 1;
    left_shift_count = 0;
}

void display_static_text(uint8_t *text)
{
    // 创建显示任务,就不用管了,其余交给中断函数
    d.display_nbit = SCREEN_WIDTH;
    d.left_shift_time = 0;
    d.head = 0;
    d.tail = 0;
    d.text_buf = text;
    new_data_in = 1;
}

void led_full_on()
{
    for (uint16_t i = 0; i < BUF_SIZE * 16; i++)
    {
        display_buf[i] = 0xFFFF;
    }
    static_display_mode();
}

void led_clear_display_buf()
{
    for (uint16_t i = 0; i < BUF_SIZE * 16; i++)
    {
        display_buf[i] = 0x0000;
    }
}

void one_row_led_on(uint8_t row)
{
    for (uint16_t i = 0; i < 8; i++)
    {
        Display16bit(row, i, 0xFFFF);
    }
    static_display_mode();
}

void static_display_mode()
{
    // 创建显示任务,就不用管了,其余交给中断函数
    d.display_nbit = SCREEN_WIDTH;
    d.left_shift_time = 0;
    d.head = 0;
    d.tail = SCREEN_WIDTH / 8;
    d.text_buf = 0;
    new_data_in = 1;
}

/* 显示跑表 */
uint32_t watch(uint8_t cmd)
{
    const uint8_t tab[] = {0, 1, 3, 4};        // 要更新数字的位置
    static uint8_t old_time[4] = {8, 8, 8, 8}; // 上一次显示的时间88:88
    static uint8_t new_time[4] = {0, 0, 0, 0}; // 现在要显示的时间00:00
    static uint8_t current_status;             // 表当前状态
    uint8_t buf[32];                           // 这是一个读取字符时的缓冲区
    uint32_t tmp;                              // 用于临时计算

    switch (cmd)
    {
    case WatchUpdate:
        if ((current_status == WatchStart) && (IO_RS485_R0T1 == 0))
        {
            while (1)
            {
                // 取时间怕中断打断,所以用临时变量验证
                tmp = tim4_count;
                if (tmp == tim4_count)
                {
                    break;
                }
            }
            for (uint8_t i = 3; i != 0xFF; i--)
            {
                new_time[i] = tmp % 10;
                if (new_time[i] != old_time[i])
                {
                    insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[(new_time[i] + '0' - 0x20) * 16], tab[i]);
                    old_time[i] = new_time[i];
                }
                tmp /= 10;
            }
        }
        break;

    case WatchReset:                         // 复位
        static_display_mode();               // 静态显示
        read_font(buf, ':');                 // 读 :
        insert_8x16_to_display_buf(buf, 2);  // 写 : 号到显存
        read_font(buf, ' ');                 // 读 ' '
        insert_8x16_to_display_buf(buf, 5);  // 写 : 号到显存
        read_font(buf, 0xEBC3);              // 汉字 秒
        insert_16x16_to_display_buf(buf, 6); // 写汉字 秒
        insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[('0' - 0x20) * 16], 0);
        insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[('0' - 0x20) * 16], 1);
        insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[('0' - 0x20) * 16], 3);
        insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[('0' - 0x20) * 16], 4);
        break;

    case WatchStart: // 启动
        tim4_count = 0;
        TIM_Cmd(TIM4, ENABLE);
        current_status = WatchStart;
        break;

    case WatchStop: // 停止
        current_status = WatchStop;
        TIM_Cmd(TIM4, DISABLE);
        break;

    case WatchGetTime: // 获取时间
        while (1)
        {
            tmp = tim4_count;
            if (tmp == tim4_count)
            {
                return tmp;
            }
        }
        break;
    }
    return 0;
}

/* 显示排球成绩 */
void led_update_volleyball_score(uint8_t hit, uint8_t all)
{
    static_display_mode();
    insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[(hit / 10 + '0' - 0x20) * 16], 1);
    insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[(hit % 10 + '0' - 0x20) * 16], 2);
    insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[('/' - 0x20) * 16], 3);
    insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[(all / 10 + '0' - 0x20) * 16], 4);
    insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[(all % 10 + '0' - 0x20) * 16], 5);
}

void led_one_row_update()
{
    static uint8_t r = 0;
    // 让显示不要打扰发送
    if (display_nbit)
    {
        uint8_t *pStart = (uint8_t *)((uint32_t)display_buf + r * BUF_SIZE * 2); // 本行循环显存的开始地址
        uint8_t *pCur = (uint8_t *)((uint32_t)pStart + head % (BUF_SIZE * 2));   // 本行循环显存要移入显示屏的数据的开始地址
        uint8_t *pEnd = (uint8_t *)((uint32_t)pStart + BUF_SIZE * 2);            // 本行循环显存的结束的下一个地址
        uint8_t display_data = *pCur;                                            // 读取第一个8位移位数据
        uint16_t n = 0;                                                          // 计数到7就需要再读一个字节
        uint16_t has_nbit = (tail - head) * 8;                                   // 显存中有多少列的点阵数据
        uint16_t n_bit = display_nbit;                                           // 要显示多少位
        uint16_t zero_bit = 0;
        // 判断要移入多少个0填充
        if (display_nbit + display_nbit_offset > has_nbit)
        {
            zero_bit = display_nbit + display_nbit_offset - has_nbit; // 要显示的0的列的个数
            n_bit = has_nbit - display_nbit_offset;                   // 要显示的列数比显存中的还多,先显示显存中的,接着显示0
        }
        if (n_bit < SCREEN_WIDTH)
        {
            zero_bit = SCREEN_WIDTH - n_bit;
        }
        display_data >>= display_nbit_offset; // 去掉要跳过的位数
        n = display_nbit_offset;              // 用于计数剩下的位
        // 移入已有数据
        while (n_bit--)
        {
            R1 = G1 = display_data; // 移位
            CLK = 0;                // 时针
            CLK = 1;                // 时针
            display_data >>= 1;
            if (++n == 8)
            {
                n = 0;
                pCur = (pCur + 1 == pEnd) ? pStart : pCur + 1;
                display_data = *pCur;
            }
        }
        // 不够的补位的0移入
        R1 = G1 = 0; // 移位
        while (zero_bit--)
        {
            CLK = 0; // 时针
            CLK = 1; // 时针
        }
        DisplayOFF;          // 关显示
        D = r >> 3;          // 设置行
        C = r >> 2;          // 设置行
        B = r >> 1;          // 设置行
        A = r >> 0;          // 设置行
        Latch;               // 锁存
        DisplayON;           // 开显示
        r++;                 // 换行
        r = r == 16 ? 0 : r; // 到15行后重新到第1行
    }
}

void wait_for_left_shift_count(uint8_t n)
{
    while (left_shift_count < n)
    {
    }
}

uint8_t is_left_shift_count_reached(uint8_t n)
{
    return left_shift_count >= n;
}

void led_update_new_task()
{
    if (new_data_in)
    {
        display_nbit = d.display_nbit;
        head = d.head;
        tail = d.tail;
        text_buf = text_buf_backup = d.text_buf;
        left_shift_time = d.left_shift_time;
        display_nbit_offset = 0;
        left_shift_count = 0;
        new_data_in = 0;
    }
}

void led_read_font_if_nessessary()
{
    // 自动读入字符到显存,显存是循环队列,自动决定位置，让读字体不要打扰发送
    if (text_buf && *text_buf)
    {
        // 是否还有空出的显存位置读入字符
        if (tail - head < BUF_SIZE * 2 - 1)
        {
            // 计算数据要存放的位置
            uint8_t position = tail % (BUF_SIZE * 2);
            uint8_t *p = (uint8_t *)display_buf + position;
            // 要读一个汉字
            if (*text_buf >= 0xA1)
            {
                // 计算芯片地址,并设置好flash芯片
                uint32_t addr = ((*text_buf - 0xA1) * 94 + (*(text_buf + 1) - 0xA1)) * 32 + ZK_OFFSET;
                // 读取32个字节，需要9.22us
                flash_read_data_via_dma(addr, pix_buf, 32);
                while (FLASH_CS == 0)
                {
                }
                // 存储16X16左边一半点阵到显存 500ns
                uint8_t *src = pix_buf + 6;
                for (uint8_t i = 0; i < 16; i++)
                {
                    *p = *src++;
                    p += BUF_SIZE * 2;
                }
                // 存储16X16右边一半点阵到显存 500ns
                p = (position == BUF_SIZE * 2 - 1) ? (uint8_t *)display_buf : (uint8_t *)display_buf + position + 1;
                src = pix_buf + 16 + 6;
                for (uint8_t i = 0; i < 16; i++)
                {
                    *p = *src++;
                    p += BUF_SIZE * 2;
                }
                tail += 2;
                text_buf += 2;
            }
            // 读一个ascii字符
            else
            {
                uint8_t *pAscii = get_ascii_font(*text_buf);
                for (uint8_t i = 0; i < 16; i++)
                {
                    *p = *pAscii++;
                    p += BUF_SIZE * 2;
                }
                tail += 1;
                text_buf++;
            }
        }
    }
    // 根据参数决定是否连续左移显示更多字符
    if (left_shift_time && (tim3_count % left_shift_time == 0))
    {
        if (tail > head)
        {
            if (display_nbit_offset < 8 - 1)
            {
                display_nbit_offset++;
            }
            else
            {
                display_nbit_offset = 0;
                head++;
            }
        }
        else
        {
            // 显示完成之后,没有新显示任务,就重新显示原来的任务
            for (uint8_t i = 0; i < BUF_SIZE * 2; i++)
            {
                clear_8x16_to_display_buf(i);
            }
            head = 0;
            tail = SCREEN_WIDTH / 8;
            left_shift_count++;
            text_buf = text_buf_backup;
        }
    }
}

void led_display_infomation_and_wait_once(uint8_t *text)
{
    display_text_left_shift(text);
    wait_for_left_shift_count(1);
}

void led_print_one_ascii(uint8_t ch, uint8_t position)
{
    insert_8x16_to_display_buf((uint8_t *)&ascii_8x16_font[(ch - 0x20) * 16], position);
}

void led_print_hex(uint16_t n, uint8_t position)
{
    char buf[5];
    sprintf(buf, "%02X", n);
    uint8_t i = 0;
    while (buf[i] != 0)
    {
        led_print_one_ascii(buf[i], position + i);
        i++;
    }
}

void led_print_dec(uint16_t n, uint8_t position)
{
    char buf[10];
    sprintf(buf, "%d", n);
    uint8_t i = 0;
    while (buf[i] != 0)
    {
        led_print_one_ascii(buf[i], position + i);
        i++;
    }
}